﻿using System;
namespace Restful.Net.TestHost
{
    //This class is based on the commons-lang org.apache.commons.lang.builder.HashCodeBuilder
    //Java class (http://www.koders.com/java/fidCE4E86F23847AE93909CE105394B668DDB0F491A.aspx).
    //
    //Reflection methods have been removed and Java syntax repaced with C#. The original file
    //contained the following license:
    //
    //Copyright 2002-2004 The Apache Software Foundation.
    //
    //Licensed under the Apache License, Version 2.0 (the "License");
    //you may not use this file except in compliance with the License.
    //You may obtain a copy of the License at
    //
    //     http://www.apache.org/licenses/LICENSE-2.0
    //
    //Unless required by applicable law or agreed to in writing, software
    //distributed under the License is distributed on an "AS IS" BASIS,
    //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    //See the License for the specific language governing permissions and
    //limitations under the License.
    //
    public class HashCodeBuilder
    {

        /// <summary>
        /// Constant to use in building the hashCode.
        ///</summary>
        private readonly int iConstant;
        /// <summary>
        /// Running total of the hashCode.
        ///</summary>
        private int iTotal;

        /// <summary>
        /// <para>Constructor.</para>
        ///
        /// <para>This constructor uses two hard coded choices for the constants
        /// needed to build a <code>hashCode</code>.</para>
        /// </summary>
        public HashCodeBuilder()
        {
            iConstant = 37;
            iTotal = 17;
        }

        /// <summary>
        /// <para>Constructor.</para>
        ///
        /// <para>Two randomly chosen, non-zero, odd numbers must be passed in.
        /// Ideally these should be different for each class, however this is
        /// not vital.</para>
        ///
        /// <para>Prime numbers are preferred, especially for the multiplier.</para>
        /// </summary>
        /// <param name="initialNonZeroOddNumber">  a non-zero, odd number used as the initial value</param>
        /// <param name="multiplierNonZeroOddNumber">  a non-zero, odd number used as the multiplier</param>
        /// <exception cref="ArgumentException"> if the number is zero or even </exception>
        ///
        public HashCodeBuilder(int initialNonZeroOddNumber, int multiplierNonZeroOddNumber)
        {
            if (initialNonZeroOddNumber == 0)
            {
                throw new ArgumentException("HashCodeBuilder requires a non zero initial value");
            }
            if (initialNonZeroOddNumber % 2 == 0)
            {
                throw new ArgumentException("HashCodeBuilder requires an odd initial value");
            }
            if (multiplierNonZeroOddNumber == 0)
            {
                throw new ArgumentException("HashCodeBuilder requires a non zero multiplier");
            }
            if (multiplierNonZeroOddNumber % 2 == 0)
            {
                throw new ArgumentException("HashCodeBuilder requires an odd multiplier");
            }
            iConstant = multiplierNonZeroOddNumber;
            iTotal = initialNonZeroOddNumber;
        }

        //-------------------------------------------------------------------------

        /// <summary>
        /// <para>Adds the result of super.hashCode() to this builder.</para>
        /// </summary>
        /// <param name="superHashCode">  the result of calling <code>super.hashCode()</code></param>
        /// <returns>this HashCodeBuilder, used to chain calls.</returns>
        /// 
        ///
        public HashCodeBuilder AppendSuper(int superHashCode)
        {
            iTotal = iTotal * iConstant + superHashCode;
            return this;
        }


        /// <summary>
        /// Append a <code>hashCode</code> for a <code>string</code>.
        /// </summary>
        /// <param name="str">the string to add to the <code>hashCode</code></param>
        /// <returns></returns>
        public HashCodeBuilder Append(string str)
        {
            if (string.IsNullOrEmpty(str))
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                Append(str.ToCharArray());
            }
            return this;
        }


        /// <summary>
        /// <para>Append a <code>hashCode</code> for an <code>Object</code>.</para>
        /// </summary>
        /// <param name="obj"> the Object to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        public HashCodeBuilder Append(object obj)
        {
            if (obj == null)
            {
                iTotal = iTotal * iConstant;

            }
            else
            {
                if (!obj.GetType().IsArray)
                {
                    //the simple case, not an array, just the element
                    iTotal = iTotal * iConstant + obj.GetHashCode();

                }
                else
                {
                    //'Switch' on type of array, to dispatch to the correct handler
                    // This handles multi dimensional arrays
                    if (obj is long[])
                    {
                        Append((long[])obj);
                    }
                    else if (obj is int[])
                    {
                        Append((int[])obj);
                    }
                    else if (obj is short[])
                    {
                        Append((short[])obj);
                    }
                    else if (obj is char[])
                    {
                        Append((char[])obj);
                    }
                    else if (obj is byte[])
                    {
                        Append((byte[])obj);
                    }
                    else if (obj is double[])
                    {
                        Append((double[])obj);
                    }
                    else if (obj is float[])
                    {
                        Append((float[])obj);
                    }
                    else if (obj is bool[])
                    {
                        Append((bool[])obj);
                    }
                    else
                    {
                        // Not an array of primitives
                        Append((object[])obj);
                    }
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>long</code>.</para>
        /// </summary>
        /// <param name="value">  the long to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(long value)
        {
            iTotal = iTotal * iConstant + ((int)(value ^ (value >> 32)));
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for an <code>int</code>.</para>
        /// </summary>
        /// <param name="value">  the int to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(int value)
        {
            iTotal = iTotal * iConstant + value;
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>short</code>.</para>
        /// </summary>
        /// <param name="value">  the short to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(short value)
        {
            iTotal = iTotal * iConstant + value;
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>char</code>.</para>
        /// </summary>
        /// <param name="value">  the char to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(char value)
        {
            iTotal = iTotal * iConstant + value;
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>byte</code>.</para>
        /// </summary>
        /// <param name="value">  the byte to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(byte value)
        {
            iTotal = iTotal * iConstant + value;
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>double</code>.</para>
        /// </summary>
        /// <param name="value">  the double to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(double value)
        {
            return Append(Convert.ToSingle(value));
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>float</code>.</para>
        /// </summary>
        /// <param name="value">  the float to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(float value)
        {
            iTotal = iTotal * iConstant + (int)value;
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>boolean</code>.</para>
        /// </summary>
        /// <param name="value">  the boolean to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(bool value)
        {
            iTotal = iTotal * iConstant + (value ? 0 : 1);
            return this;
        }

        ///// <summary>
        ///// <para>Append a <code>hashCode</code> for an <code>Object</code> array.</para>
        ///// </summary>
        ///// <param name="array">  the array to add to the <code>hashCode</code></param>
        ///// <returns>this<\returns>
        /////
        //public HashCodeBuilder Append(object[] array)
        //{
        //    if (array == null)
        //    {
        //        iTotal = iTotal * iConstant;
        //    }
        //    else
        //    {
        //        for (int i = 0; i < array.Length; i++)
        //        {
        //            Append(array[i]);
        //        }
        //    }
        //    return this;
        //}

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>long</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(long[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for an <code>int</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(int[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>short</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(short[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>char</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(char[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>byte</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(byte[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>double</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(double[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>float</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>
        ///
        public HashCodeBuilder Append(float[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Append a <code>hashCode</code> for a <code>boolean</code> array.</para>
        /// </summary>
        /// <param name="array">  the array to add to the <code>hashCode</code></param>
        /// <returns>this</returns>

        public HashCodeBuilder Append(bool[] array)
        {
            if (array == null)
            {
                iTotal = iTotal * iConstant;
            }
            else
            {
                for (int i = 0; i < array.Length; i++)
                {
                    Append(array[i]);
                }
            }
            return this;
        }

        /// <summary>
        /// <para>Return the computed <code>hashCode</code>.</para>
        /// </summary>
        /// <returns><code>hashCode</code> based on the fields appended</returns>
        ///
        public int ToHashCode()
        {
            return iTotal;
        }

    }

}